﻿#region Creditos

/* Autor: Edilberto Sánchez Forero	*/
/* Email: esanchezfo@gmail.com		*/
/* Licencia: LGPL, Software Libre	*/
/* País: Colombia, Bogotá			*/

#endregion

#region Librerias

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using System.IO;
using System.Threading.Tasks;

#endregion


namespace ESF.General
{
	public static class ProcessHelper
	{
		#region Delegados

		public delegate void DoAndNotifyEmailDelegate (ref string pSubject, ref List<string> pTo, ref StringBuilder pBody, ref TextWriter pLogWriter, DateTime pStartDateTime);

		public delegate void DoAndNotifyEmailDelegateSimple (ref StringBuilder pBody, DateTime pStartDateTime);


		public delegate void ReportProgressDelegate (string pMessage = "", int pTotalWorkCount = 0, int pReadyWorkCount = 0, int pTotalErrors = 0, long pItemElapsedMs = 0);

		#endregion


		#region Calculos basicos

		public static int ProcessorsCoresLimit
		{
			get
			{
				int result;


				result = (int)Math.Ceiling (Environment.ProcessorCount * ReadConfig.PercentageProcessorCoreLimit100 / 100.0);

				if (result <= 0) { return 1; }

				return result;
			}
		}


		public static ParallelOptions DefaultParallelOptions
		{
			get
			{	
				return new ParallelOptions () 
				{ 
					MaxDegreeOfParallelism = ProcessHelper.ProcessorsCoresLimit,
					CancellationToken = new CancellationToken ()
				};
			}
		}


		public static ParallelOptions GetParallelOptions(int pMaxDegreeOfParallelism, CancellationTokenSource pCTS = null)
		{
			return new ParallelOptions()
			{
				MaxDegreeOfParallelism = pMaxDegreeOfParallelism,
				CancellationToken = (pCTS == null ? new CancellationToken () : pCTS.Token)
			};
		}


		public static ParallelOptions GetDefaultParallelOptions (CancellationTokenSource pCTS = null)
		{
			ParallelOptions result;


			result = DefaultParallelOptions;

			if (pCTS != null)
			{
				result.CancellationToken = pCTS.Token;
			}


			return result;
		} 

		#endregion


		#region Calculo de Avance

		public static double GetAdvancePercentage100 (long pTotalWorkCount, long pReadyWorkCount)
		{
			double result;
			
			
			if (pTotalWorkCount == 0)
			{
				return 0;
			}

			result = Math.Ceiling(pReadyWorkCount * 100.0 / pTotalWorkCount);

			if (result > 100) { return 100; }

			return result;
		}

		#endregion


		#region Funciones para disparar procesos
		
		public static void CallProcesses (List<ProcessStartInfo> pobjProcInfos, bool pblnWait = true)
		{
			CallProcesses (pobjProcInfos, 0, pblnWait);
		}
		
		
		public static void CallProcesses (List<ProcessStartInfo> pobjProcInfos, uint pintMaxWaitMilliseconds, bool pblnWait = true)
		{
			if (pobjProcInfos != null && pobjProcInfos.Count == 0) { return; }
		
			Log.WriteLineDebug ("Preparando la ejecución de los siguientes comandos:\n");
		
			foreach (ProcessStartInfo objProcInfo in pobjProcInfos)
			{
				Log.WriteLineDebug ("{0} {1}", objProcInfo.FileName, objProcInfo.Arguments);
			}
			
			foreach (ProcessStartInfo objProcInfo in pobjProcInfos)
			{
				CallProcess (objProcInfo, pintMaxWaitMilliseconds, pblnWait);
			}
		}
		
		
		public static void CallProcess (ProcessStartInfo pobjProcInfo, bool pblnWait = true)
		{
			CallProcess (pobjProcInfo, 0, pblnWait);
		}
		
		
		public static void CallProcess (ProcessStartInfo pobjProcInfo, uint pintMaxWaitMilliseconds, bool pblnWait = true)
		{
			Process proProc;
			
			Stopwatch sw = new Stopwatch ();
			
		
			proProc = null;
			
			pobjProcInfo.WindowStyle = ProcessWindowStyle.Hidden;
			pobjProcInfo.ErrorDialog = true;
			pobjProcInfo.CreateNoWindow = true;
			pobjProcInfo.UseShellExecute = false;
			pobjProcInfo.RedirectStandardError = true;
			pobjProcInfo.RedirectStandardOutput = true;
				
			proProc = null;

			sw.Start ();

			try
			{	
				Log.WriteLineDebug ("Iniciando la ejecución a las {0} de: {1} {2}", 
					DateTime.Now, pobjProcInfo.FileName, pobjProcInfo.Arguments);
				
				using (proProc = Process.Start (pobjProcInfo))
				{
					if (proProc.StandardOutput != null)
					{
						Log.WriteLineDebug ("Output: {0}", proProc.StandardOutput.ReadToEnd());
					}

					if (proProc.StandardError != null)
					{
						Log.WriteLineDebug ("Error: {0}", proProc.StandardError.ReadToEnd ());
					}

					if (pblnWait)
					{
						proProc.WaitForExit ();
					}
					else if (pintMaxWaitMilliseconds > 0)
					{
						proProc.WaitForExit ((int) pintMaxWaitMilliseconds);
					}
				}
			}
			catch (Exception excEx)
			{
				if (pobjProcInfo != null)
				{
					throw new GenericException (
						GenericException.CRI_EXTERNAL_APP_ERROR, 
						string.Format ("{0}\n\nLlamado no exitoso a: \n\n{1} {2}{3}", 
						 excEx.Message, 
						 pobjProcInfo.FileName, 
						 pobjProcInfo.Arguments,
						 (pintMaxWaitMilliseconds > 0 ? "\nMaximo tiempo de ejecución: " + pintMaxWaitMilliseconds + "ms" : string.Empty)),
						 excEx);
				}
			
				throw new GenericException (GenericException.CRI_EXTERNALSERVICE_ERROR, string.Format ("No fue ejecutado el proceso debido a {0}", excEx.Message), excEx);
			}
			finally
			{
				sw.Stop ();

				Log.WriteLineDebug ("Terminada la ejecución a las {0}, duración en: {1}\n", 
					DateTime.Now, TextHelper.ToStringMS (sw.ElapsedMilliseconds));
			}
		}
		
		#endregion


		#region Threads

		public static void SetProcessorCoreLimit ()
		{
			SetProcessorCoreLimit (ReadConfig.GetAppSettingInt32 ("ProcessorCoreLimit", 1));
		}


		public static void SetProcessorCoreLimit (int pProcessorCoreLimit)
		{
			if (pProcessorCoreLimit > Environment.ProcessorCount)
			{
				pProcessorCoreLimit = Environment.ProcessorCount;
			}

			Process.GetCurrentProcess().ProcessorAffinity = 
				(System.IntPtr) Convert.ToInt32 (
					new String ('0', pProcessorCoreLimit) + 
					new String ('1', pProcessorCoreLimit), 2);
		}

		#endregion


		#region COM

		public static void ReleaseCOMObjects (params object [] pCOMObjs)
		{
			if (pCOMObjs == null || pCOMObjs.Length == 0) { return; }
		
			
			for (int i = 0; i < pCOMObjs.Length; i++)
			{
				try
				{
					while (System.Runtime.InteropServices.Marshal.ReleaseComObject(pCOMObjs [i]) > 0) {};
				
					GC.Collect();
					GC.WaitForPendingFinalizers();
				}
				catch (Exception)
				{
				}
				finally
				{	
					pCOMObjs [i] = null;
				}
			}
		}
		
		#endregion


		#region Funciones para procesar y notificar al email

		public static void DoAndNotifyEmailBasic(string pHistoryCategory, string pSubject, DoAndNotifyEmailDelegateSimple pDelegate, params string[] pToEmails)
		{
			DoAndNotifyEmailBasic(pHistoryCategory, pSubject, pDelegate, false, pToEmails);
		}


		public static void DoAndNotifyEmailBasic(string pHistoryCategory, string pSubject, DoAndNotifyEmailDelegateSimple pDelegate, bool pIsHTML, params string[] pToEmails)
		{
			DoAndNotifyEmail (pHistoryCategory, pSubject, 
				delegate (ref string pSubjectx, ref List<string> pTox, ref StringBuilder pBodyx, ref TextWriter pLogWriterx, DateTime pStartDateTimex)
				{
					pDelegate (ref pBodyx, pStartDateTimex);
				},
				pIsHTML,
				pToEmails);
		}


		public static void DoAndNotifyEmail (string pHistoryCategory, string pSubject, DoAndNotifyEmailDelegate pDelegate, params string [] pToEmails)
		{
			DoAndNotifyEmail (pHistoryCategory, pSubject, pDelegate, false, pToEmails);
		}


		public static void DoAndNotifyEmail (string pHistoryCategory, string pSubject, DoAndNotifyEmailDelegate pDelegate, bool pIsHTML, params string [] pToEmails)
		{
			string processID;


			processID = Guid.NewGuid ().ToString ();

			Log.WriteStartHistory ("Invocando proceso para enviar el correo {0} con ID {1}...", pSubject, processID);


			#region Verificar Emails To

			if (DataHelper.IsEmpty (pToEmails))
			{
				throw new ArgumentException (string.Format ("To(s) email were not provided for message with subject {0}", pSubject));
			}

			foreach (string to in pToEmails)
			{
				if (!TextHelper.IsEmail (to))
				{
					throw new ArgumentException(string.Format("To email: {0} is invalid for message with subject: {1}", to, pSubject));	
				}
			}

			#endregion


			Task.Factory.StartNew ( () =>
			{
				Stopwatch stopWatch;

				StringBuilder message;

				TextWriter logWriter;

				string subject;
				string temp;
				
				List<string> toEmails;

				DateTime now;

				
				subject = pSubject;
				
				message = new StringBuilder ();

				logWriter = Log.GetLogWriter (pHistoryCategory, processID);

				toEmails = (DataHelper.IsEmpty (pToEmails) ? new List<string> () : new List<string> (pToEmails));
				
				now = DateTime.Now;

				stopWatch = new Stopwatch ();

				stopWatch.Start ();


				try
				{
					pDelegate (ref pSubject, ref toEmails, ref message, ref logWriter, now);
				}
				catch (Exception pEx)
				{
					pEx.Data.Add("ProcessID", processID);
					
					Log.Error(subject, pEx); 
					
					subject = string.Format("[ Error ] {0}", pSubject);

					temp = string.Format ("{0}{0}El proceso no fue completado ({2}-{3}).{0}{1}", 
						(pIsHTML ? "<br/>" : Environment.NewLine), 
						GenericException.GetClientMessage (pEx), 
						Environment.MachineName,
						Environment.UserName);

					logWriter.WriteLine (temp);

					message.AppendLine (temp);
				}
				finally
				{
					stopWatch.Stop ();

					temp = string.Format ("{0}{0}El procesamiento inicio en {1:yyyy-MM-dd HH:mm:ss}{0}Y termino en {2:yyyy-MM-dd HH:mm:ss} tomando en total {3}{0}{0}Referencia del proceso: {4}",
						(pIsHTML ? "<br/>" : Environment.NewLine), 
						now,
						DateTime.Now,
						TextHelper.ToStringMS(stopWatch.ElapsedMilliseconds), 
						processID);

					message.AppendLine (temp);

					logWriter.WriteLine (temp);

					EmailSender.Send(subject, message.ToString(), pIsHTML, true, false, toEmails.ToArray ());

					logWriter.Flush ();
					logWriter.Close ();
				}

			});
		}


		public static bool ReTryWhen (Action p, Func<Exception, int, bool> pStillTrying, int pTrySleepMs = 1000, double pIncrementalWaitFactor = 0.1)
		{
			int tryCount = 0;


			while (true)
			{
				try
				{
					tryCount++;

					p ();

					return true;
				}
				catch (Exception pEx)
				{
					if (!pStillTrying (pEx, tryCount) || tryCount > 10000)
					{
						throw pEx;
					}
				}

				Thread.Sleep ((int) Math.Ceiling (pTrySleepMs * pIncrementalWaitFactor));
			}
		}

		#endregion


		#region Iniciar Proceso de Archivo

		public static void StartProcess (string pFileName)
		{
			try
			{
				Process.Start (pFileName);
			}
			catch (Exception pEx)
			{
				throw new GenericInfoException (string.Format ("No se ha ejecutado la aplicación para presentar el archivo {0}", pFileName), pEx);
			}
		}

		#endregion
	}
}
